package com.road_manage.apk;

import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.webkit.URLUtil;
import android.widget.Toast;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.database.Cursor;
import android.webkit.WebResourceRequest;
import android.webkit.WebResourceResponse;

// [新增] 导入 MimeTypeMap
import android.webkit.MimeTypeMap;

// [新增] 导入新的 Activity Result API 相关类
import androidx.activity.result.ActivityResult;
import androidx.activity.result.ActivityResultCallback;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.core.content.FileProvider;
import androidx.webkit.WebViewAssetLoader;

import java.text.SimpleDateFormat;
import java.util.Date;
import android.util.Log;
import android.webkit.CookieManager;
import java.net.HttpURLConnection;
import java.net.URL;
import android.content.Context;
import android.util.Base64;
import android.webkit.JavascriptInterface;
import android.webkit.GeolocationPermissions;
import android.location.Location;
import android.location.LocationManager;
import android.location.LocationListener;
import androidx.exifinterface.media.ExifInterface;
import android.text.TextUtils;
import android.view.KeyEvent;
import android.view.WindowManager;
import android.os.Handler;
import android.os.Looper;
import java.io.*;

// [恢复] Zip相关导入
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import androidx.documentfile.provider.DocumentFile;
import java.util.Stack;
import android.content.ContentUris;
import java.util.ArrayList;
import java.util.List;
import android.content.DialogInterface;
import androidx.appcompat.app.AlertDialog;
import android.provider.DocumentsContract;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
// [恢复] Deque/ArrayDeque 导入 (用于 Zip)
import java.util.Deque;
import java.util.ArrayDeque;
import java.net.URLDecoder;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.Locale;

public class MainActivity extends AppCompatActivity {

	// [新增] 权限请求码
	private static final int PERMISSIONS_REQUEST_CODE = 1001;

	private boolean isExit = false;
	private final Object lock = new Object();
	
	private String open_in_app_url_head = "https://appassets.androidplatform.net";

    private WebView webView;
	private RoadDataInterface roadDataInterface;
    private ValueCallback<Uri[]> filePathCallback;
    private String cameraPhotoPath;

	private LocationManager locationManager;
    private LocationListener locationListener;
	private double lat;
	private double lon;
	private double alt;
	private long locationTime;

    // [新增] 声明 ActivityResultLauncher
    private ActivityResultLauncher<Intent> fileChooserLauncher;
    private ActivityResultLauncher<Uri> cameraLauncher;
    private ActivityResultLauncher<Uri> folderPickerLauncher;

    @SuppressLint({"SetJavaScriptEnabled", "WebViewClientOnReceivedSslError"})
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // [新增] 初始化 ActivityResultLauncher，必须在 onCreate 中完成
        initializeLaunchers();

        setContentView(R.layout.activity_main);
        
		// [修改] 调用更新后的权限检查方法
        checkAndRequestPermissions();	

        // [新增] 每次冷启动时，自动在后台清空缓存
        clearCacheOnStartup();
        
        locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);	
		locationListener = new LocationListener() {
			@Override
			public void onLocationChanged(Location location) {
				lat = location.getLatitude();
				lon = location.getLongitude();
				alt = location.getAltitude();
				locationTime = location.getTime();
			}
		};
        
        webView = findViewById(R.id.webview);
		roadDataInterface = new RoadDataInterface(this);

        final WebViewAssetLoader assetLoader = new WebViewAssetLoader.Builder()
                .addPathHandler("/assets/", new WebViewAssetLoader.AssetsPathHandler(this))
                .setDomain("appassets.androidplatform.net")
                .setHttpAllowed(false)
                .build();
		
        WebSettings webSettings = webView.getSettings();
        
        webSettings.setAllowFileAccess(true);
        webSettings.setDomStorageEnabled(true);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
        }
		webSettings.setGeolocationEnabled(true);
        webSettings.setJavaScriptEnabled(true);
        webSettings.setUseWideViewPort(true);
        webSettings.setLoadWithOverviewMode(true);
		webSettings.setDatabaseEnabled(true);
		
		webView.addJavascriptInterface(roadDataInterface, "android_road_data");
		
        webView.addJavascriptInterface(new Object() {
            @JavascriptInterface
            public void downloadFile(String url, String fileName, String parentpath, String datatype) {
				// [修改] 此方法现在调用 "混合" 逻辑
                downloadBlob(url, fileName, parentpath ,datatype);
            }

			// [恢复] 压缩相关接口
			@JavascriptInterface
			public void startOutWithZip() {
				try {
					outWithZip();
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
			
			@JavascriptInterface
			public void exitapp() {
				AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
				builder.setTitle("确认退出");
				builder.setMessage("您确定要退出应用吗？");
				builder.setPositiveButton("是", (dialog, which) -> finish());
				builder.setNegativeButton("否", (dialog, which) -> dialog.dismiss());
				builder.show();
			}

			// [恢复] 压缩相关接口
			@JavascriptInterface
			public void clearCacheDirectory() {
				clearzip();
			}

			@JavascriptInterface
			public void getDirUrl() {
				openFolderPicker();
			}
			@JavascriptInterface
			public void fileProcessed() {
				synchronized (lock) {
					lock.notify();
				}
			}
			
			@JavascriptInterface
			public void startwakelock() {
                runOnUiThread(() -> getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON));
			}

			@JavascriptInterface
			public void endwakelock() {
                runOnUiThread(() -> getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON));
			}
        }, "Android");	
		
        String desktopUserAgent = "Mozilla/5.0 (Linux; Android 10; Pixel 4 XL Build/QD1A.190805.007) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.120 Mobile Safari/537.36";
        webSettings.setUserAgentString(desktopUserAgent);
        
        CookieManager cookieManager = CookieManager.getInstance();
        cookieManager.setAcceptCookie(true);
        cookieManager.setAcceptThirdPartyCookies(webView, true);
        
        webView.setWebViewClient(new WebViewClient() {
            @Override
            @Nullable
            public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
                if (request.getUrl() != null) {
                    return assetLoader.shouldInterceptRequest(request.getUrl());
                }
                return null;
            }

            @Override
            public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
                String url = request.getUrl().toString();
                if (url.startsWith("https://appassets.androidplatform.net/")) {
                    return false;
                }
                // [移除] file:/// 检查，因为我们现在使用 assetLoader
                // if (url.startsWith(open_in_app_url_head)) {
                //     return false;
                // }
                try {
                    Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
                    startActivity(intent);
                } catch (Exception e) {
                    Log.e("UrlLoadingError", "无法处理该 URL: " + url, e);
                }
                return true;
            }
        });		
        
		webView.setDownloadListener((url, userAgent, contentDisposition, mimeType, contentLength) -> {
			
            // [精简] 移除了 Android 9 (P) 及以下的权限检查。
            // 在 Android 10 (API 29) 及更高版本上，DownloadManager 写入
            // 'DIRECTORY_DOWNLOADS' 不需要显式的 WRITE_EXTERNAL_STORAGE 权限。

			if (!url.startsWith("data:")) {
				String filename = null;
				if (contentDisposition != null) {
					Pattern filenameStarPattern = Pattern.compile("filename\\*=([^']*)''(.+)");
					Matcher filenameStarMatcher = filenameStarPattern.matcher(contentDisposition);
					if (filenameStarMatcher.find()) {
						try {
							String encoding = filenameStarMatcher.group(1);
							String encodedFilename = filenameStarMatcher.group(2);
							filename = URLDecoder.decode(encodedFilename, encoding != null ? encoding : StandardCharsets.UTF_8.name());
						} catch (Exception e) {
							e.printStackTrace();
						}
					}
				}
				if (filename == null || filename.isEmpty()) {
					filename = URLUtil.guessFileName(url, contentDisposition, mimeType);
				}
				startDownload(url, filename);
			}
		});

        webView.setWebChromeClient(new WebChromeClient() {
            @Override
            public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
                if (MainActivity.this.filePathCallback != null) {
                    MainActivity.this.filePathCallback.onReceiveValue(null);
                }
                MainActivity.this.filePathCallback = filePathCallback;

                if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
					ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.CAMERA}, PERMISSIONS_REQUEST_CODE);
                    return false; 
                }
				String[] acceptTypes = fileChooserParams.getAcceptTypes();
				boolean isMultiple = (fileChooserParams.getMode() == FileChooserParams.MODE_OPEN_MULTIPLE);
				
                if (acceptTypes != null && acceptTypes.length > 0 && !acceptTypes[0].isEmpty()) {
                    if (getchoosemode(acceptTypes, ".for_webview_camera")) {
                        openCamera();
                    } else if (getchoosemode(acceptTypes, ".for_webview_dir")) {
                        // [修改] 确保 .for_webview_dir 也能被正确处理
                        openFileChooser(isMultiple, new String[]{".for_webview_dir"});
                    }
                    else {
                        openFileChooser(isMultiple, acceptTypes);
                    }
                } else {
                    openFileChooser(isMultiple, new String[]{"*/*"});
                }
                return true;
            }

			@Override
			public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) {
				if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
					callback.invoke(origin, true, false);
				} else {
                    // [修正] 如果没有权限，应该请求权限，而不是直接拒绝
                    ActivityCompat.requestPermissions(MainActivity.this, 
                        new String[]{Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION}, 
                        PERMISSIONS_REQUEST_CODE);
					callback.invoke(origin, false, false);
				}
				super.onGeolocationPermissionsShowPrompt(origin, callback);
			}
        });

        webView.loadUrl("https://appassets.androidplatform.net/assets/index.html");
    }

    // [新增] 专门用于初始化所有 Launcher 的方法
    private void initializeLaunchers() {
        // 1. 初始化文件/相机选择器的 Launcher
        fileChooserLauncher = registerForActivityResult(
                new ActivityResultContracts.StartActivityForResult(),
                result -> {
                    Uri[] results = null;
                    Intent data = result.getData();
                    if (result.getResultCode() == Activity.RESULT_OK) {
                        if (data != null) {
                            if (data.getClipData() != null) {
                                int itemCount = data.getClipData().getItemCount();
                                results = new Uri[itemCount];
                                for (int i = 0; i < itemCount; i++) {
                                    results[i] = data.getClipData().getItemAt(i).getUri();
                                }
                            } else if (data.getData() != null) {
                                results = new Uri[]{data.getData()};
                            }
                        } else if (cameraPhotoPath != null) { // 处理拍照返回 (data 为 null)
                            writeLocationToExif(cameraPhotoPath);
                            results = new Uri[]{getCameraPhotoUri()};
                        }
                    }

                    if (filePathCallback != null) {
                        filePathCallback.onReceiveValue(results);
                        filePathCallback = null;
                    }
                    if (locationManager != null && locationListener != null) {
                        locationManager.removeUpdates(locationListener);
                    }
                    // 重置 cameraPhotoPath 避免下次误用
                    cameraPhotoPath = null;
                });

        // 2. 初始化纯相机 Launcher
        cameraLauncher = registerForActivityResult(
                new ActivityResultContracts.TakePicture(),
                success -> {
                    Uri[] results = null;
                    if (success && cameraPhotoPath != null) {
                        writeLocationToExif(cameraPhotoPath);
                        results = new Uri[]{getCameraPhotoUri()};
                    }
                    if (filePathCallback != null) {
                        filePathCallback.onReceiveValue(results);
                        filePathCallback = null;
                    }
                    if (locationManager != null && locationListener != null) {
                        locationManager.removeUpdates(locationListener);
                    }
                    // 重置 cameraPhotoPath
                    cameraPhotoPath = null;
                });

        // 3. 初始化文件夹选择器 Launcher
        folderPickerLauncher = registerForActivityResult(
                new ActivityResultContracts.OpenDocumentTree(),
                folderUri -> {
                    Uri[] results = null;
                    if (folderUri != null) {
                        try {
                            // [新增] 持久化权限
                            final int takeFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
                            getContentResolver().takePersistableUriPermission(folderUri, takeFlags);

                            DocumentFile pickedFolder = DocumentFile.fromTreeUri(this, folderUri);
                            if (pickedFolder == null) throw new Exception("Could not create DocumentFile");

                            Uri[] all_uri = traverseFolder(pickedFolder);
                            List<String> relativePaths = new ArrayList<>();
                            String rootDocumentId = DocumentsContract.getTreeDocumentId(folderUri);
                            String dir_name = pickedFolder.getName();

                            for (Uri fileUri : all_uri) {
                                String fileDocumentId = DocumentsContract.getDocumentId(fileUri);
                                if (fileDocumentId != null && fileDocumentId.startsWith(rootDocumentId)) {
                                    String relativePath = fileDocumentId.substring(rootDocumentId.length());
                                    if (relativePath.startsWith(":")) {
                                        relativePath = relativePath.substring(1);
                                    }
                                    relativePaths.add(dir_name + "/" + relativePath);
                                }
                            }

                            JSONArray jsonArray = new JSONArray(relativePaths);
                            String jsonString = jsonArray.toString();
                            String base64String = Base64.encodeToString(jsonString.getBytes(StandardCharsets.UTF_8), Base64.NO_WRAP);
                            String tojs = "window.getphonepath('" + base64String + "')";
                            webView.evaluateJavascript(tojs, null);
                            results = all_uri;

                        } catch (Exception e) {
                            runOnUiThread(() -> webView.evaluateJavascript("window.getFolderFilesError('文件路径获取错误：" + e.getMessage() + "')", null));
                        }
                    } else { // 用户取消选择
                        webView.evaluateJavascript("window.getphonepath('')", null);
                    }

                    // 文件夹选择也可能触发文件上传回调
                    if (filePathCallback != null) {
                        filePathCallback.onReceiveValue(results);
                        filePathCallback = null;
                    }
                });
    }

	@Override
	public boolean onKeyDown(int keyCode, KeyEvent event) {
		if (keyCode == KeyEvent.KEYCODE_BACK) {
			webView.evaluateJavascript("window.phoneback()", null);
			return true;
		}
		return super.onKeyDown(keyCode, event);
	}
	
    @Override
    protected void onDestroy() {
        if (webView != null) {
            // [新增] 停止加载并清除视图，更彻底地释放资源
            webView.stopLoading();
            webView.clearHistory();
            webView.clearCache(true);
            webView.loadUrl("about:blank");
            webView.removeAllViews();
            webView.destroy();
            webView = null;
        }
		if(roadDataInterface !=null){
			roadDataInterface.close();
		}
		if (locationManager != null && locationListener != null) {
			locationManager.removeUpdates(locationListener);
		}
        super.onDestroy();
    }
	
    // [替换] 彻底解决权限问题的检查方法 (适配 Android 10-14+)
    private void checkAndRequestPermissions() {
		List<String> permissionsNeeded = new ArrayList<>();

		// 1. 位置权限 (用于GPS定位和EXIF)
		if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
			permissionsNeeded.add(Manifest.permission.ACCESS_FINE_LOCATION);
		}
		if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
			permissionsNeeded.add(Manifest.permission.ACCESS_COARSE_LOCATION);
		}

		// 2. 相机权限 (用于文件选择器中的拍照)
		if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
			permissionsNeeded.add(Manifest.permission.CAMERA);
		}

		if (!permissionsNeeded.isEmpty()) {
			ActivityCompat.requestPermissions(this, permissionsNeeded.toArray(new String[0]), PERMISSIONS_REQUEST_CODE);
		}
    }
	
	private boolean getchoosemode(String[] acceptTypes, String targetType) {
		if (acceptTypes != null && targetType != null) {
			for (String type : acceptTypes) {
                if (type == null || type.isEmpty()) continue;
				if (targetType.equals(type)) {
					return true;
				}
			}
		}
		return false;
	}
	
    private void openFileChooser(boolean isMultiple,String[] acceptTypes) {
        Intent takePictureIntent = null;
        File photoFile = null;

        // 检查是否包含 "image/*" 或 "*/*"，如果包含，才添加相机选项
        boolean canAcceptImage = false;
        for (String type : acceptTypes) {
            if (type.equals("*/*") || type.startsWith("image/")) {
                canAcceptImage = true;
                break;
            }
        }

        if (canAcceptImage && new Intent(MediaStore.ACTION_IMAGE_CAPTURE).resolveActivity(getPackageManager()) != null) {
            takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
			if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
                locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000, 0.0f, locationListener);
            }
            try {
                photoFile = createImageFile();
                cameraPhotoPath = photoFile.getAbsolutePath();
            } catch (IOException ex) {
                Toast.makeText(this, "无法创建图片文件", Toast.LENGTH_SHORT).show();
                photoFile = null;
                cameraPhotoPath = null;
            }
            if (photoFile != null) {
                Uri photoURI = FileProvider.getUriForFile(this, getPackageName() + ".fileprovider", photoFile);
                takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
                takePictureIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
            }
        }

		if(getchoosemode(acceptTypes, ".for_webview_dir")){
            // [修改] 调用新的 Launcher
            folderPickerLauncher.launch(null); // OpenDocumentTree 不需要输入
		}else{
			Intent contentSelectionIntent = new Intent(Intent.ACTION_GET_CONTENT);
			contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE);
			contentSelectionIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, isMultiple);
			
            // [修改] 过滤掉自定义的 acceptType
            List<String> standardMimeTypes = new ArrayList<>();
            for (String type : acceptTypes) {
                if (type != null && !type.startsWith(".")) {
                    standardMimeTypes.add(type);
                }
            }
            // 如果过滤后为空，则默认为 "*/*"
            if (standardMimeTypes.isEmpty()) {
                standardMimeTypes.add("*/*");
            }
            
            contentSelectionIntent.setType(standardMimeTypes.get(0)); // 设置主 MimeType
            if (standardMimeTypes.size() > 1) {
                contentSelectionIntent.putExtra(Intent.EXTRA_MIME_TYPES, standardMimeTypes.toArray(new String[0]));
            }

			Intent[] intentArray = takePictureIntent != null ? new Intent[]{takePictureIntent} : new Intent[0];
			Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER);
			chooserIntent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent);
			chooserIntent.putExtra(Intent.EXTRA_TITLE, "选择文件");
			chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentArray);
            
            // [修改] 调用新的 Launcher
			fileChooserLauncher.launch(chooserIntent);
		}
    }	
	
	private void openCamera() {
		if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
            locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000, 0.0f, locationListener);
        }

        File photoFile = null;
        try {
            photoFile = createImageFile();
            cameraPhotoPath = photoFile.getAbsolutePath();
        } catch (IOException ex) {
            Toast.makeText(this, "无法创建图片文件", Toast.LENGTH_SHORT).show();
            // [修复] 拍照失败时，必须通知 WebView，否则会卡住
            if (filePathCallback != null) {
                filePathCallback.onReceiveValue(null);
                filePathCallback = null;
            }
            return;
        }

        if (photoFile != null) {
            Uri photoURI = FileProvider.getUriForFile(this, getPackageName() + ".fileprovider", photoFile);
            // [修改] 调用新的 Launcher
            cameraLauncher.launch(photoURI);
        } else {
            Toast.makeText(this, "相机不可用", Toast.LENGTH_SHORT).show();
            // [修复] 拍照失败时，必须通知 WebView
            if (filePathCallback != null) {
                filePathCallback.onReceiveValue(null);
                filePathCallback = null;
            }
        }
	}
	
    private File createImageFile() throws IOException {
        String timeStamp = new SimpleDateFormat("yyyyMMddHHmmss", Locale.getDefault()).format(new Date());
        String imageFileName = "JPEG_" + timeStamp + "_";
        File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES);
        // [修改] 使用 createTempFile 保证文件名唯一
        File imageFile = File.createTempFile(imageFileName, ".jpg", storageDir);
        return imageFile;
    }

	private void startDownload(String url,String fileName) {
		try {
			Uri uri = Uri.parse(url);
			android.app.DownloadManager.Request request = new android.app.DownloadManager.Request(uri);
			String cookies = CookieManager.getInstance().getCookie(url);		
			if (cookies != null) {
				request.addRequestHeader("Cookie", cookies);
			} else {
				Log.w("Download", "No cookies found for URL: " + url);
			}

			request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName);
			request.setNotificationVisibility(android.app.DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
			request.setAllowedNetworkTypes(android.app.DownloadManager.Request.NETWORK_WIFI | android.app.DownloadManager.Request.NETWORK_MOBILE);
            // [新增] 允许下载覆盖同名文件
            request.setAllowedOverMetered(true);
            request.setAllowedOverRoaming(true);

			android.app.DownloadManager downloadManager = (android.app.DownloadManager) getSystemService(DOWNLOAD_SERVICE);
			downloadManager.enqueue(request);
			Toast.makeText(this, "下载已开始", Toast.LENGTH_SHORT).show();
		} catch (Exception e) {
			e.printStackTrace();
			Toast.makeText(this, "下载出现错误", Toast.LENGTH_SHORT).show();
		}
	}
	
    private void downloadBlob(String base64String, String fileName, String parentpath ,String datatype){
        new Thread(() -> {
            try {
                // [修改] 将 Base64 解码移到顶部，确保两个分支都能访问 decodedBytes
                byte[] decodedBytes = decodeBase64(base64String); 

				if ("file".equals(datatype)) {
					// --- 1. "file" 类型: 直接写入公共 "Downloads" 目录 (自动重命名) ---
					ContentResolver resolver = getContentResolver();
					ContentValues values = new ContentValues();
					String customFolder = "road_manage";
					String relativePath = Environment.DIRECTORY_DOWNLOADS + "/" + customFolder + (parentpath == null ? "" : parentpath);

					// 自动检测 MimeType
					String mimeType = "application/octet-stream";
					String extension = MimeTypeMap.getFileExtensionFromUrl(fileName);
					if (extension != null) {
						String mimeFromExtension = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension.toLowerCase());
						if (mimeFromExtension != null) {
							mimeType = mimeFromExtension;
						}
					}

					// 生成唯一文件名（如果存在同名文件则添加序号）
					String finalFileName = generateUniqueFileName(resolver, relativePath, fileName);

					values.put(MediaStore.Downloads.DISPLAY_NAME, finalFileName);
					values.put(MediaStore.Downloads.MIME_TYPE, mimeType);
					values.put(MediaStore.Downloads.RELATIVE_PATH, relativePath);
					values.put(MediaStore.Downloads.IS_PENDING, 0);

					// 插入新文件
					Uri fileUri = resolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, values);
					if (fileUri == null) {
						throw new IOException("MediaStore 无法创建文件");
					}

					// 写入数据
					try (OutputStream os = resolver.openOutputStream(fileUri)) {
						if (os == null) {
							throw new IOException("无法打开输出流");
						}
						os.write(decodedBytes);
					}
					
					runOnUiThread(() -> Toast.makeText(MainActivity.this, 
						"文件已保存到 " + relativePath + " (文件名: " + finalFileName + ")", 
						Toast.LENGTH_SHORT).show());

				}else if ("dir".equals(datatype)) {
                    // --- 2. "dir" 类型: 将数据写入应用的 "私有缓存" 目录 ---
                    // 路径: /Android/data/YOUR_APP_PACKAGE/files/road_manage/cache/PARENT_PATH/fileName
                    // 注意: MainActivity.this.getExternalFilesDir(null)
                    File storageDir = new File(getExternalFilesDir(null), "road_manage/cache" + (parentpath == null ? "" : parentpath));
                    
                    if (!storageDir.exists()) {
                        if (!storageDir.mkdirs()) {
                            throw new IOException("无法创建私有缓存目录: " + storageDir.getAbsolutePath());
                        }
                    }

                    File targetFile = new File(storageDir, fileName); // [新增] 创建目标文件
                    
                    // 写入数据到私有文件
                    try (FileOutputStream fos = new FileOutputStream(targetFile)) { // [新增]
                        fos.write(decodedBytes); // [新增]
                    }

                } else {
                    // 如果 datatype 既不是 "file" 也不是 "dir"，则抛出错误
                    throw new IllegalArgumentException("不支持的 datatype: " + (datatype == null ? "null" : datatype));
                }
                runOnUiThread(() -> webView.evaluateJavascript("window.onDownloadComplete()", null));

            } catch (Exception e) {
                // 注意: MainActivity.this.runOnUiThread() 和 webView
                Log.e("DownloadBlobError", "downloadBlob 出错", e);
                runOnUiThread(() -> {
                    Toast.makeText(MainActivity.this, "保存失败: " + e.getMessage(), Toast.LENGTH_SHORT).show();
                    String errorMessage = (e.getMessage() != null ? e.getMessage().replace("'", "\\'") : "Unknown error");
                    webView.evaluateJavascript("window.onDownloadError('" + errorMessage + "')", null);
                });
            }
		}).start();
	}
	private String generateUniqueFileName(ContentResolver resolver, String relativePath, String originalFileName) {
		// 分离文件名和扩展名
		String baseName;
		String extension = "";
		
		int dotIndex = originalFileName.lastIndexOf(".");
		if (dotIndex > 0 && dotIndex < originalFileName.length() - 1) {
			// 有扩展名
			baseName = originalFileName.substring(0, dotIndex);
			extension = originalFileName.substring(dotIndex); // 包含点号，如 ".pdf"
		} else {
			// 没有扩展名
			baseName = originalFileName;
		}

		// 检查文件是否存在
		String candidateFileName = originalFileName;
		int counter = 1;
		
		while (fileExistsInMediaStore(resolver, relativePath, candidateFileName)) {
			// 文件存在，生成新名字：baseName(counter).extension
			candidateFileName = baseName + "(" + counter + ")" + extension;
			counter++;
			
			// 防止无限循环（理论上不会发生，但作为安全措施）
			if (counter > 9999) {
				throw new RuntimeException("无法生成唯一文件名：已尝试 9999 次");
			}
		}
		
		return candidateFileName;
	}
	private boolean fileExistsInMediaStore(ContentResolver resolver, String relativePath, String fileName) {
		Uri queryUri = MediaStore.Downloads.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY);
		
		// 确保路径末尾有斜杠（MediaStore 查询需要）
		String normalizedPath = relativePath.endsWith("/") ? relativePath : relativePath + "/";
		
		String selection = MediaStore.Downloads.RELATIVE_PATH + " = ? AND " 
						 + MediaStore.Downloads.DISPLAY_NAME + " = ?";
		String[] selectionArgs = new String[]{normalizedPath, fileName};
		
		try (Cursor cursor = resolver.query(queryUri, 
											new String[]{MediaStore.Downloads._ID}, 
											selection, 
											selectionArgs, 
											null)) {
			return cursor != null && cursor.getCount() > 0;
		} catch (Exception e) {
			Log.e("FileExistsCheck", "检查文件是否存在时出错", e);
			return false; // 出错时假设文件不存在，允许尝试写入
		}
	}
	
	// [恢复] outWithZip 方法 (使用现代存储逻辑)
	private void outWithZip() {
		new Thread(() -> {
			File cacheDir = new File(getExternalFilesDir(null), "road_manage/cache/");
			
			if (!cacheDir.exists() || !cacheDir.isDirectory()) {
				runOnUiThread(() -> Toast.makeText(this, "源文件夹 (App 缓存) 不存在", Toast.LENGTH_SHORT).show());
                runOnUiThread(() -> webView.evaluateJavascript("window.zipWorkDone()", null)); // [修复] 即使失败也要回调
				return;
			}
			
			try {
				File[] listFiles = cacheDir.listFiles();
				if (listFiles == null || listFiles.length == 0) {
					 runOnUiThread(() -> Toast.makeText(this, "缓存目录为空", Toast.LENGTH_SHORT).show());
                     runOnUiThread(() -> webView.evaluateJavascript("window.zipWorkDone()", null)); // [修复] 即使失败也要回调
					 return;
				}

                String relativeZipPath = Environment.DIRECTORY_DOWNLOADS + "/road_manage";

				for (File folder : listFiles) {
					if (folder.isDirectory()) {
						String zipFileName = folder.getName() + ".zip";
						
						ContentResolver resolver = getContentResolver();
						ContentValues values = new ContentValues();
						values.put(MediaStore.Downloads.DISPLAY_NAME, zipFileName);
						values.put(MediaStore.Downloads.MIME_TYPE, "application/zip");
						values.put(MediaStore.Downloads.RELATIVE_PATH, relativeZipPath);
                        values.put(MediaStore.Downloads.IS_PENDING, 1); // 标记为正在写入

						Uri existingUri = MediaStore.Downloads.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY);
						String selection = MediaStore.Downloads.RELATIVE_PATH + " = ? AND " + MediaStore.Downloads.DISPLAY_NAME + " = ?";
						String[] selectionArgs = new String[]{relativeZipPath + "/", zipFileName}; // [修正] 路径必须以 / 结尾
						
						try (Cursor cursor = resolver.query(existingUri, new String[]{MediaStore.Downloads._ID}, selection, selectionArgs, null)) {
                            if (cursor != null && cursor.moveToFirst()) {
                                long id = cursor.getLong(0);
                                Uri deleteUri = ContentUris.withAppendedId(existingUri, id);
                                resolver.delete(deleteUri, null, null);
                            }
                        }

						Uri zipFileUri = resolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, values);
						if (zipFileUri == null) {
							throw new IOException("无法在 MediaStore 中创建 Zip 文件");
						}

						try (OutputStream fos = resolver.openOutputStream(zipFileUri);
							 ZipOutputStream zos = new ZipOutputStream(new BufferedOutputStream(fos))) {
							
							addFolderToZip(zos, folder); 

						} catch (IOException e) {
							e.printStackTrace();
							resolver.delete(zipFileUri, null, null);
							runOnUiThread(() -> Toast.makeText(this, "压缩文件夹 " + folder.getName() + "出错" + e, Toast.LENGTH_SHORT).show());
							return;
						}

                        // [新增] 写入完成后，更新 IS_PENDING 状态
                        values.clear();
                        values.put(MediaStore.Downloads.IS_PENDING, 0);
                        resolver.update(zipFileUri, values, null, null);
					}
				}
			
			} catch (Exception e) {
				e.printStackTrace();
				runOnUiThread(() -> Toast.makeText(this, "压缩过程中出现错误", Toast.LENGTH_SHORT).show());
				return;
			}
			
			// 压缩成功后才删除缓存
			try{
				deleteDirectories(cacheDir);
			}catch(Exception e){
				e.printStackTrace();
				runOnUiThread(() -> Toast.makeText(this, "删除缓存过程中出现错误", Toast.LENGTH_SHORT).show());			
			}
			
			runOnUiThread(() -> 
				webView.evaluateJavascript("window.zipWorkDone()", value -> {
					Toast.makeText(this, "已导出到 " + Environment.DIRECTORY_DOWNLOADS + "/road_manage/", Toast.LENGTH_LONG).show();
				})
			);
		}).start();
    }
	
	// [恢复] addFolderToZip 方法
	private void addFolderToZip(ZipOutputStream zos, File rootFolder) throws IOException {
		Deque<File> folderStack = new ArrayDeque<>();
		folderStack.push(rootFolder);
        // [修改] 基础路径应为 rootFolder 本身，以便 Zip 包内的路径是相对的
		String rootPath = rootFolder.getPath();

		while (!folderStack.isEmpty()) {
			File currentFolder = folderStack.pop();
			File[] files = currentFolder.listFiles();
			if (files == null) continue;

            // [修改] 计算相对路径
            String relativePath = currentFolder.getPath().substring(rootPath.length()).replace(File.separator, "/");
            // 移除开头的 "/"
            if (relativePath.startsWith("/")) {
                relativePath = relativePath.substring(1);
            }

            // [修改] 确保在文件列表之前添加目录条目
            if (!relativePath.isEmpty() && files.length == 0) {
                zos.putNextEntry(new ZipEntry(relativePath + "/"));
                zos.closeEntry();
            }

			for (File file : files) {
                // [修改] 计算条目路径
                String entryPath = file.getPath().substring(rootPath.length()).replace(File.separator, "/");
                if (entryPath.startsWith("/")) {
                    entryPath = entryPath.substring(1);
                }

				if (file.isDirectory()) {
                    // [修改] 显式添加目录条目
                    zos.putNextEntry(new ZipEntry(entryPath + "/"));
                    zos.closeEntry();
					folderStack.push(file);
				} else {
					boolean isfiledeleted = false;
					try (FileInputStream fis = new FileInputStream(file)) {
						ZipEntry zipEntry = new ZipEntry(entryPath);
						zos.putNextEntry(zipEntry);
						byte[] buffer = new byte[8192];
						int bytesRead;
						while ((bytesRead = fis.read(buffer)) != -1) {
							zos.write(buffer, 0, bytesRead);
						}
						zos.closeEntry();
						isfiledeleted = true;
					}
					if (isfiledeleted) {
						if (!file.delete()) {
							Log.e("DeleteFileError", "Failed to delete file: " + file.getAbsolutePath());
						}
					}
				}
			}
		}
	}
	
	// [恢复] deleteDirectories 方法
	private boolean deleteDirectories(File directory) {
        if (directory == null || !directory.exists()) {
            return true;
        }
        if (!directory.isDirectory()) {
            return directory.delete();
        }
        File[] children = directory.listFiles();
        if (children != null) {
            for (File child : children) {
                deleteDirectories(child);
            }
        }
        return directory.delete();
	}

    /**
     * [新增] 应用启动时在后台静默清理缓存目录。
     * 此方法不显示 Toast，也不回调 JS，只记录日志。
     */
    private void clearCacheOnStartup() {
        new Thread(() -> {
            try {
                // 路径: /Android/data/com.road_manage.apk/files/road_manage/cache/
                File cacheDir = new File(getExternalFilesDir(null), "road_manage/cache/");
                if (!cacheDir.exists() || !cacheDir.isDirectory()) {
                    Log.i("CacheClear", "Cache directory does not exist, no need to clear.");
                    return;
                }
                
                // deleteDirectories 会递归删除并返回成功与否
                if (deleteDirectories(cacheDir)) {
                    Log.i("CacheClear", "Cache directory successfully cleared on startup.");
                } else {
                    Log.e("CacheClear", "Failed to clear cache directory on startup.");
                }
            } catch (Exception e) {
                Log.e("CacheClear", "Exception while clearing cache on startup.", e);
            }
        }).start();
    }
	
	// [恢复] clearzip 方法 (供 JS 调用)
	private void clearzip(){
		new Thread(() -> {
			try {
				// 路径: /Android/data/com.road_manage.apk/files/road_manage/cache/
				File cacheDir = new File(getExternalFilesDir(null), "road_manage/cache/");
				if (!cacheDir.exists() || !cacheDir.isDirectory()) {
					runOnUiThread(() -> webView.evaluateJavascript("window.onzipdelet('缓存目录已是空的')", null));
					return;
				}
				if (deleteDirectories(cacheDir)) {
					runOnUiThread(() -> webView.evaluateJavascript("window.onzipdelet('缓存目录已清空')", null));
				} else {
					runOnUiThread(() -> webView.evaluateJavascript("window.onzipdelet('清空缓存目录时出错')", null));
				}
			} catch (Exception e) {
				runOnUiThread(() -> webView.evaluateJavascript("window.onzipdelet('清空缓存目录时发生异常: " + e.getMessage() + "')", null));
			}
		}).start();	
	}
	
	private void openFolderPicker() {
		folderPickerLauncher.launch(null);
	}	

	private Uri getCameraPhotoUri() {
        if (cameraPhotoPath == null) return null;
		File file = new File(cameraPhotoPath);
		return FileProvider.getUriForFile(this, getPackageName() + ".fileprovider", file);
	}
	
    public static boolean hasGPSData(String imagePath) {
        if (imagePath == null) return false;
        try {
            ExifInterface exif = new ExifInterface(imagePath);
            String lat = exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE);
            return lat != null && !lat.isEmpty();
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
    }
	
	private void writeLocationToExif(String photoPath) {
        if (photoPath == null) return;
		if(hasGPSData(photoPath)) {
            showToast("照片已包含GPS数据，不再写入。");
			return;
		}
		if(locationTime == 0){
			showToast("未获得GPS数据，无法写入照片。");
			return;
		}
		if(System.currentTimeMillis() - locationTime > 20000){
			showToast("GPS数据已过期（超过20秒），请重试。");
			return;
		}
		try {
			ExifInterface exif = new ExifInterface(photoPath);

			exif.setAttribute(ExifInterface.TAG_GPS_LATITUDE, convertToDMS(lat));
			exif.setAttribute(ExifInterface.TAG_GPS_LATITUDE_REF, lat >= 0 ? "N" : "S");

			exif.setAttribute(ExifInterface.TAG_GPS_LONGITUDE, convertToDMS(lon));
			exif.setAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF, lon >= 0 ? "E" : "W");

			exif.setAttribute(ExifInterface.TAG_GPS_ALTITUDE, String.valueOf((int)Math.abs(alt)) + "/1");
			exif.setAttribute(ExifInterface.TAG_GPS_ALTITUDE_REF, alt >= 0 ? "0" : "1");
			
			exif.saveAttributes();
			showToast("GPS信息已写入照片。");
		} catch (IOException e) {
			showToast("EXIF写入失败: " + e.getMessage());
			Log.e("EXIFError", "Could not write EXIF", e);
		}
	}

    private String convertToDMS(double coord) {
        double absolute = Math.abs(coord);
        int degrees = (int) absolute;
        absolute = (absolute - degrees) * 60;
        int minutes = (int) absolute;
        absolute = (absolute - minutes) * 60;
        // [修改] 使用更精确的秒格式
        double seconds = absolute;
        return String.format(Locale.US, "%d/1,%d/1,%.0f/1000", degrees, minutes, (seconds * 1000));
    }
	
    // [注] 此方法似乎未被使用，但保留
    public static String convertToExifFormat(double coord) {
        int degrees = (int) coord;
        double minutesDecimal = (coord - degrees) * 60;
        int minutes = (int) minutesDecimal;
        double seconds = (minutesDecimal - minutes) * 60;

        return String.format(Locale.US, "%d/1,%d/1,%.0f/1", degrees, minutes, seconds);
    }
	
	private List<String> buildRelativePaths(DocumentFile root, Uri[] uris) {
		List<String> paths = new ArrayList<>();
        if (root == null || uris == null) return paths;
		String rootId = DocumentsContract.getTreeDocumentId(root.getUri());
		String dirName = root.getName() != null ? root.getName() : "unknown";

		for (Uri uri : uris) {
            if (uri == null) continue;
			String docId = DocumentsContract.getDocumentId(uri);
			if (docId == null || !docId.startsWith(rootId)) continue;
			
			String relativePath = docId.substring(rootId.length()).replaceFirst(":", "");
			paths.add(dirName + "/" + relativePath);
		}
		return paths;
	}

	private void showToast(String message) {
		runOnUiThread(() -> Toast.makeText(this, message, Toast.LENGTH_SHORT).show());
	}

	private int getfilecount(DocumentFile folder) {
		int count = 0;
		if (folder == null) return 0;
		Deque<DocumentFile> stack = new ArrayDeque<>();
		stack.push(folder);
		while (!stack.isEmpty()) {
			DocumentFile currentFolder = stack.pop();
			DocumentFile[] files = currentFolder.listFiles();

			if (files != null) {
				for (DocumentFile file : files) {
					if (file.isDirectory()) {
						stack.push(file);
					} else {
						count++;
					}
				}
			}
		}
		return count;
	}
	
	private Uri[] traverseFolder(DocumentFile rootFolder) {
		List<Uri> uriList = new ArrayList<>();
		Deque<DocumentFile> stack = new ArrayDeque<>();

		if (rootFolder != null) {
            stack.push(rootFolder);
        }

		while (!stack.isEmpty()) {
			DocumentFile currentFolder = stack.pop();
            if (currentFolder == null) continue;
			for (DocumentFile file : currentFolder.listFiles()) {
                if (file == null) continue;
				if (file.isFile()) {
					uriList.add(file.getUri());
				} else if (file.isDirectory()) {
					stack.push(file);
				}
			}
		}
		return uriList.toArray(new Uri[0]);
	}
	
	private void waitForJavaScript() {
		synchronized (lock) {
			try {
				lock.wait();
			} catch (InterruptedException e) {
				Thread.currentThread().interrupt();
				e.printStackTrace();
			}
		}
	}
	
	private String readFileToBase64(DocumentFile file) {
        if (file == null) return null;
        if (file.length() > 50 * 1024 * 1024) {
            return "toobig|";
        }
		try (InputStream inputStream = getContentResolver().openInputStream(file.getUri());
			 ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
            if (inputStream == null) return null;
			byte[] buffer = new byte[8192];
			int bytesRead;
			while ((bytesRead = inputStream.read(buffer)) != -1) {
				byteArrayOutputStream.write(buffer, 0, bytesRead);
			}
			return Base64.encodeToString(byteArrayOutputStream.toByteArray(), Base64.NO_WRAP);
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}
	
	private String getMimeType(DocumentFile file) {
        if (file == null) return "application/octet-stream";
		String mimeType = getContentResolver().getType(file.getUri());
		return mimeType != null ? mimeType : "application/octet-stream";
	}	

	/**
	 * [新增] 
	 * 辅助方法：安全地解码 Base64 字符串，自动处理 "data:..." 前缀。
	 */
	private byte[] decodeBase64(String base64Data) {
        if (base64Data == null) {
            return new byte[0];
        }
		String base64String;
		if (base64Data.contains(",")) {
			base64String = base64Data.substring(base64Data.indexOf(',') + 1);
		} else {
			base64String = base64Data;
		}
		return Base64.decode(base64String, Base64.DEFAULT);
	}

    // [替换] 使用这个新的、更详细的权限结果处理方法
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);

        if (requestCode == PERMISSIONS_REQUEST_CODE) {
            List<String> deniedPermissions = new ArrayList<>();
            for (int i = 0; i < grantResults.length; i++) {
                if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {
                    deniedPermissions.add(permissions[i]);
                }
            }

            if (!deniedPermissions.isEmpty()) {
                StringBuilder message = new StringBuilder("以下权限被拒绝，可能导致部分功能（如拍照、定位）无法使用：\n");
                for (String permission : deniedPermissions) {
                    message.append("\n- ").append(getPermissionUserFriendlyName(permission));
                }
                message.append("\n\n请考虑在应用设置中手动授予这些权限。");

                new AlertDialog.Builder(this)
                        .setTitle("权限请求失败")
                        .setMessage(message.toString())
                        .setPositiveButton("知道了", (dialog, which) -> dialog.dismiss())
                        .setNegativeButton("前往设置", (dialog, which) -> {
                            try {
                                Intent intent = new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
                                intent.setData(Uri.parse("package:" + getPackageName()));
                                startActivity(intent);
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                        })
                        .show();
            } else {
				Toast.makeText(this, "权限已授予", Toast.LENGTH_SHORT).show();
            }
        }
    }

    // [新增] 辅助方法，将权限名称转换为用户可读的字符串
    private String getPermissionUserFriendlyName(String permission) {
        switch (permission) {
            case Manifest.permission.ACCESS_FINE_LOCATION:
            case Manifest.permission.ACCESS_COARSE_LOCATION:
                return "位置信息 (用于GPS定位和照片EXIF)";
            case Manifest.permission.CAMERA:
                return "相机 (用于拍照上传)";
            default:
                return permission.substring(permission.lastIndexOf('.') + 1);
        }
    }
	
    @Override
    protected void onPause() {
        super.onPause();
        if (webView != null) {
            webView.onPause();
        }
        CookieManager.getInstance().flush();
    }

    @Override
    protected void onResume() {
        super.onResume();
        if (webView != null) {
            webView.onResume();
        }
        CookieManager.getInstance().flush();
    }
}